﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using HtmlAgilityPack;

namespace Kooboo.HtmlFunc
{
    public static class NodeExtension
    {
        public static void Clear(this HtmlAttributeCollection attrs)
        {
            for (var i = attrs.Count - 1; i > -1; i--)
            {
                attrs[i].Remove();
            }
        }

        public static string Attr(this HtmlNode node, string name)
        {
            return node.GetAttributeValue(name, null);
        }

        public static void Attr(this HtmlNode node, string name, string value)
        {
            node.SetAttributeValue(name, value);
        }

        public static bool HasAttr(this HtmlNode node, string attrName)
        {
            return !string.IsNullOrEmpty(node.Attr(attrName));
        }

        public static void RemoveAttr(this HtmlNode node, string attrName)
        {
            node.Attributes.Remove(attrName);
        }

        private static bool IsEmpty(HtmlNode node)
        {
            if (node.NodeType == HtmlNodeType.Text)
            {
                var text = ((HtmlTextNode)node).Text.Trim();
                if (string.IsNullOrEmpty(text))
                {
                    return true;
                }
            }

            return false;
        }

        public static HtmlTextNode CreateNode(string html)
        {
            var node = (HtmlTextNode)HtmlTextNode.CreateNode(" ");
            node.Text = html;
            return node;
        }

        public static void ReplaceInner(HtmlNode start, HtmlNode end, string html)
        {
            ReplaceInner(start, end, CreateNode(html));
        }

        public static string GetInnerContent(HtmlNode start, HtmlNode end, bool readHtml = true)
        {
            var builder = new StringBuilder(string.Empty);
            var next = start.NextSibling;
            while (next != null && next != end)
            {
                builder.Append(readHtml ? next.OuterHtml : next.InnerText);
                next = next.NextSibling;
            }
            return builder.ToString().Trim();
        }

        public static void RemoveItemSiblings(Kooboo.HtmlFunc.Providers.IEnumerable obj)
        {
            var sameLevel = obj.ItemStartNode.ParentNode == obj.StartNode.ParentNode;
            // remove previous
            var prev = obj.ItemStartNode.PrevNoEmpty();
            while (sameLevel ? (prev != obj.StartNode) : (prev != null))
            {
                var cache = prev;
                prev = prev.PreviousSibling;
                cache.RemoveWithPrevWhitespace();
            }
            // remove next
            var next = obj.ItemEndNode.NextNoEmpty();
            while (sameLevel ? (next != obj.EndNode) : (next != null))
            {
                var cache = next;
                next = next.NextSibling;
                cache.RemoveWrap();
            }
        }

        public static void ReplaceInner(HtmlNode start, HtmlNode end, HtmlNode inner)
        {
            if (start == end)
            {
                // attribute mode is obsoleted

                var firstChild = start.ChildNodes.FirstElement();
                if (firstChild != null)
                {
                    start.InsertBefore(inner, firstChild);
                    var lastChild = start.ChildNodes.LastElement();
                    while (firstChild != lastChild)
                    {
                        var cache = lastChild;
                        lastChild = lastChild.PreviousSibling;
                        cache.RemoveWrap();
                    }
                    firstChild.RemoveWrap();
                }
                else
                {
                    var original = new List<HtmlNode>();
                    for (var i = 0; i < start.ChildNodes.Count; i++)
                        original.Add(start.ChildNodes[i].Clone());

                    start.AppendChild(inner);

                    for (var i = 0; i < original.Count; i++)
                        start.AppendChild(original[i]);
                }
            }
            else
            {
                var firstNoEmpty = start.NextNoEmpty();
                if (firstNoEmpty == end)
                {
                    var original = new List<HtmlNode>();
                    var next = start.NextSibling;
                    while (next != end)
                    {
                        original.Add(next.Clone());
                        next = next.NextSibling;
                    }

                    end.ParentNode.InsertBefore(inner, end);

                    for (var i = 0; i < original.Count; i++)
                        end.ParentNode.InsertBefore(original[i], end);
                }
                else
                {
                    var lastNoEmpty = end.PrevNoEmpty();

                    var firstSpacing = string.Empty;
                    if (firstNoEmpty.NodeType == HtmlNodeType.Text)
                    {
                        var text = ((HtmlTextNode)firstNoEmpty).Text;
                        var len = text.TrimStart().Length;
                        firstSpacing = text.Substring(0, text.Length - len);
                    }

                    var lastSpacing = string.Empty;
                    if (lastNoEmpty.NodeType == HtmlNodeType.Text)
                    {
                        var text = ((HtmlTextNode)lastNoEmpty).Text;
                        var len = text.TrimEnd().Length;
                        lastSpacing = text.Substring(len);
                    }

                    start.ParentNode.InsertBefore(inner, firstNoEmpty);
                    firstNoEmpty.RemoveTo(lastNoEmpty);

                    if (!string.IsNullOrEmpty(firstSpacing))
                    {
                        var node = NodeExtension.CreateNode(firstSpacing);
                        start.ParentNode.InsertBefore(node, inner);
                    }

                    if (!string.IsNullOrEmpty(lastSpacing))
                    {
                        var node = NodeExtension.CreateNode(lastSpacing);
                        start.ParentNode.InsertAfter(node, inner);
                    }
                }
            }
        }

        public static HashSet<string> AllClasses(this HtmlNode node)
        {
            var oldClass = (node.GetAttributeValue("class", null) ?? string.Empty).Trim();
            var oldClasses = oldClass.Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries).ToList();
            var hashSet = new HashSet<string>(StringComparer.CurrentCultureIgnoreCase);
            foreach (var cls in oldClasses)
            {
                if (!hashSet.Contains(cls))
                    hashSet.Add(cls);
            }
            return hashSet;
        }

        public static void AddClass(this HtmlNode node, string className)
        {
            var classes = node.AllClasses();
            if (!classes.Contains(className))
                classes.Add(className);
            node.SetAttributeValue("class", string.Join(" ", classes));
        }

        public static void RemoveClass(this HtmlNode node, string className)
        {
            var classes = node.AllClasses();
            classes.Remove(className);
            node.SetAttributeValue("class", string.Join(" ", classes));
        }

        public static void RemoveTo(this HtmlNode start, HtmlNode end)
        {
            var next = start;
            while (next != null && next != end)
            {
                var cache = next;
                next = next.NextSibling;
                cache.RemoveWrap();
            }
            end.RemoveWrap();
        }

        public static void RemoveWithPrevWhitespace(this HtmlNode node)
        {
            node.RemovePrevWhitespace();
            node.RemoveWrap();
        }

        public static void RemoveWithNextWhitespace(this HtmlNode node)
        {
            node.RemoveNextWhitespace();
            node.RemoveWrap();
        }

        public static void RemovePrevWhitespace(this HtmlNode node)
        {
            var prev = node.PreviousSibling;
            while (prev != null && prev.NodeType == HtmlNodeType.Text)
            {
                var cache = (HtmlTextNode)prev;
                prev = prev.PreviousSibling;
                if (string.IsNullOrWhiteSpace(cache.Text))
                    cache.RemoveWrap();
            }
        }

        public static void RemoveNextWhitespace(this HtmlNode node)
        {
            var next = node.NextSibling;
            while (next != null && next.NodeType == HtmlNodeType.Text)
            {
                var cache = (HtmlTextNode)next;
                next = next.NextSibling;
                if (string.IsNullOrWhiteSpace(cache.Text))
                    cache.RemoveWrap();
            }
        }

        private static void RemoveWrap(this HtmlNode node)
        {
            node.Remove();
        }

        public static HtmlNode NextNoEmpty(this HtmlNode node)
        {
            var next = node.NextSibling;
            while (next != null)
            {
                if (!IsEmpty(next))
                {
                    return next;
                }
                next = next.NextSibling;
            }
            return null;
        }

        public static HtmlNode PrevNoEmpty(this HtmlNode node)
        {
            var prev = node.PreviousSibling;
            while (prev != null)
            {
                if (!IsEmpty(prev))
                {
                    return prev;
                }
                prev = prev.PreviousSibling;
            }
            return null;
        }

        public static HtmlNode NextElement(this HtmlNode node)
        {
            var next = node.NextSibling;
            while (next != null)
            {
                if (next.NodeType != HtmlNodeType.Text)
                    return next;
                next = next.NextSibling;
            }
            return null;
        }

        public static HtmlNode PrevElement(this HtmlNode node)
        {
            var prev = node.PreviousSibling;
            while (prev != null)
            {
                if (prev.NodeType != HtmlNodeType.Text)
                    return prev;
                prev = prev.PreviousSibling;
            }
            return null;
        }

        public static bool Is(this HtmlNode node, string type)
        {
            if (string.IsNullOrEmpty(node.Name))
            {
                return false;
            }
            else
            {
                return node.Name.IgnoreCaseEquals(type);
            }
        }

        public static HtmlNode FirstElement(this HtmlNodeCollection nodes)
        {
            for (var i = 0; i < nodes.Count; i++)
            {
                if (nodes[i].NodeType != HtmlNodeType.Text)
                    return nodes[i];
            }
            return null;
        }

        public static HtmlNode LastElement(this HtmlNodeCollection nodes)
        {
            for (var i = nodes.Count - 1; i > -1; i--)
            {
                if (nodes[i].NodeType != HtmlNodeType.Text)
                    return nodes[i];
            }
            return null;
        }

        public static HtmlNode FindTag(this HtmlNodeCollection nodes, string tag)
        {
            if (string.IsNullOrEmpty(tag))
            {
                return null;
            }

            for (var i = 0; i < nodes.Count; i++)
            {
                if (nodes[i].Name.IgnoreCaseEquals(tag))
                    return nodes[i];
            }

            return null;
        }

        public static string ReadString(this HtmlDocument document)
        {
            var builder = new StringBuilder();
            var writer = new StringWriter(builder);
            document.Save(writer);
            return builder.ToString();
        }

        public static string ReadString(this HtmlNode node)
        {
            var builder = new StringBuilder();
            var writer = new StringWriter(builder);
            node.WriteTo(writer);
            return builder.ToString();
        }

        public static string ReadString(this IEnumerable<HtmlNode> nodes)
        {
            var builder = new StringBuilder();
            var writer = new StringWriter(builder);
            foreach (var node in nodes)
                node.WriteTo(writer);
            return builder.ToString();
        }

        public static string ReadString(HtmlNode start, HtmlNode end, bool all = false)
        {
            var builder = new StringBuilder(string.Empty);
            var writer = new StringWriter(builder);

            if (all)
            {
                start.WriteTo(writer);
            }

            var next = start.NextSibling;
            while (next != null && next != end)
            {
                next.WriteTo(writer);
                next = next.NextSibling;
            }

            if (all)
            {
                end.WriteTo(writer);
            }

            return builder.ToString();
        }
    }
}
